#include "ET++.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "TextView.h"

#include "Class.h"
#include "TextCmd.h"
#include "CmdNo.h"
#include "String.h"
#include "View.h"
#include "Window.h"
#include "Menu.h"
#include "Error.h"
#include "ChangeDialog.h"
#include "Document.h"
#include "RegularExp.h"
#include "ClipBoard.h"
#include "TextFormatter.h"
#include "Env.h"
#include "Data.h"
#include "Math.h"
#include "Timer.h"
#include "System.h"

#include "SortedOList.h"
#include "TextItem.h"
#include "MenuBar.h"

#include "caret.xpm3"
SmartBitmap CaretImage(caret);

static int InputMap[] = {
0,       /* 0x00 */
0,       /* 0x01 */
0,       /* 0x02 */
0,       /* 0x03 */
0,       /* 0x04 */
0,       /* 0x05 */
0,       /* 0x06 */
0,       /* 0x07 */
0,       /* 0x08 */
'\t',    /* 0x09 */
'\r',    /* 0x0a */
0,       /* 0x0b */
0x0c,    /* 0x0c */
'\n',    /* 0x0d */
0,       /* 0x0e */
0,       /* 0x0f */
0,       /* 0x10 */
0,       /* 0x11 */
0,       /* 0x12 */
0,       /* 0x13 */
0,       /* 0x14 */
0,       /* 0x15 */
0,       /* 0x16 */
0,       /* 0x17 */
0,       /* 0x18 */
0,       /* 0x19 */
0,       /* 0x1a */
0,       /* 0x1b */
0,       /* 0x1c */
0,       /* 0x1d */
0,       /* 0x1e */
0,       /* 0x1f */
};

static int *inputmap= InputMap;
static Ink *caretColor;

void SwapSelPoints(SelPoint &p1, SelPoint &p2)
{
	SelPoint tmp;
	tmp= p1; p1= p2; p2= tmp;
}

//---- TypingTimer -------------------------------------------------------------

const int cMaxBatchedIns= 16;   // maximum number of batched inserts

class TypingTimer : public Timer {
public:
	TypingTimer(TextView *ct, int ms);
	~TypingTimer();
	bool Notify();
	void FlushBatchedChars();
	void DelChar();
	void AddChar(int ch);
	int Size()
		{ return cnt; }
protected:
	TextView *textView;
	Text *scratchText;
	int cnt;
};

TypingTimer::TypingTimer(TextView *ct, int ms) : Timer((long)ms, eSync)
{
	textView= ct;
	cnt= 0;
	scratchText= 0;
}

TypingTimer::~TypingTimer()
{
	SafeDelete(scratchText);
}

bool TypingTimer::Notify()
{
	if (cnt > 0)
		FlushBatchedChars();
	return TRUE;
}

void TypingTimer::AddChar(int ch)
{
	if (cnt > 0 && scratchText == 0)
		FlushBatchedChars();
	if (scratchText == 0)
		scratchText= textView->GetText()->MakeScratchText(0, cMaxBatchedIns);
	cnt++;
	if (ch != '\0')
		scratchText->Append(ch);
	Reset();
}

void TypingTimer::DelChar()
{
	if (cnt > 0 && scratchText)
		FlushBatchedChars();
	cnt++;
	Reset();
}

void TypingTimer::FlushBatchedChars()
{
	if (cnt > 0) {
		if (scratchText) {
			textView->Paste(scratchText);
			SafeDelete(scratchText);
		} else {
			textView->DelChar(cnt);
		}
		cnt= 0;
		textView->RevealSelection();
		gWindowSystem->Update();
	}
}

//----- TextView Methods -------------------------------------------------------

NewMetaImpl(TextView, StaticTextView, (TB(updateSelection), TB(inTextSelector),
	TB(enabled), TP(stopChars), TP(typing), TP(findChange),
	T(start.ch), T(start.line), T(start.viewp),
	T(end.ch), T(end.line), T(end.viewp)));

TextView::TextView(EvtHandler *h, Rectangle r, Text *t, bool w, TextViewFlags f,
	Point b, TViewAlign ta, int i) : StaticTextView(h, r, t, w, f, b, ta, i)
{
	Init();
}

void TextView::InitNew()
{
	View::InitNew();
	inTextSelector= FALSE;
}

void TextView::Init()
{
	PrivSetSelection(0, 0, FALSE);
	typing= 0;
	stopChars= 0;
	updateSelection= TRUE;
	text->AddObserver(this);
	findChange= 0;
	active= TRUE;
	inTextSelector= FALSE;
	enabled= FALSE;
	SetFlag(eTextViewDragAndDrop, Env::GetValue("TextView.DragAndDrop", TRUE));
	SetFlag(eVObjKbdFocus);

	int bto= Env::GetValue("TextView.BatchTimeout", 50);
	if (bto < 10)
		SetFlag(eTextViewNoBatch);
	timer= new TypingTimer(this, bto);
}

TextView::~TextView()
{
	SafeRemove(timer);
	if (text) {
		text->RemoveObserver(this);
		text= 0;
	}
	SafeDelete(stopChars);
	if (findChange) {
		findChange->Close();
		gSystem->AddCleanupObject(findChange);
		findChange= 0;
	}
}

void TextView::InvalidateSelection()
{
	if (AnySelection())
		InvalidateRange(start.line, start.viewp, end.line, end.viewp);
}

void TextView::Draw(Rectangle r)
{
	if (GrHasColor() && CanShowSelection() && AnySelection())
		Invert(start.line, start.viewp, end.line, end.viewp);
	StaticTextView::Draw(r);
	if (CanShowSelection() && AnySelection()) {
		DrawCaret(start.viewp, start.line, On);
		if (!GrHasColor())
			Invert(start.line, start.viewp, end.line, end.viewp);
	}
}

void TextView::DrawCaret(Point p, int line, HighlightState)
{
	if (! inTextSelector && AnySelection() && Caret()) {
		Bitmap *cbm= CaretImage;
		Point caretsize(cbm->Size());
		int bh= BaseHeight(line);
		p+= GetInnerOrigin();

		if (caretColor == 0) {
			if (GrHasColor())
				caretColor= new_Ink("TextView.CaretColor",
						new_RGBColor(255, 0, 0));
			else
				caretColor= ePatXor;
		}

		if (LineHeight(line) - bh >= caretsize.y) {
			Rectangle r(p.x-caretsize.y, p.y+bh, caretsize.x, caretsize.y);
			GrPaintBitMap(r, cbm, caretColor);
		}
		p.x--;
		GrPaintLine(caretColor, 0, eDefaultCap, p, Point(p.x, p.y+bh));
	}
}

void TextView::SetKeyMap(int *map)
{
	inputmap= map;
}

//---- highlight the characters in the given range
void TextView::Invert(int from, Point fp, int to, Point tp)
{
	if (AnySelection() && !Caret()) {
		Rectangle r;
		Ink *c= gHighlightColor;
		
		if ((from == to && fp.x > tp.x) || from > to) { // normalize range
			SwapRange(from, to);
			Swap(fp, tp);
		}

		if (from == to) {
			r= Rectangle(LineToPoint(from)+Point (fp.x,0),
						Point(tp.x-fp.x, LineHeight(from)));
			GrPaintRect(r+GetInnerOrigin(), c);
			return;
		}
		r= Rectangle(LineToPoint(from)+ Point(fp.x,0),
					Point(GetInnerExtent().x-fp.x, LineHeight(from)));
		GrPaintRect(r+GetInnerOrigin(), c);
		r= Rectangle(LineToPoint(from + 1), Point(GetInnerExtent().x,
					LineToPoint(to).y - LineToPoint(from + 1).y));

		GrPaintRect(r+GetInnerOrigin(), c);
		r= Rectangle(LineToPoint(to), Point(tp.x, LineHeight(to)));
		GrPaintRect(r+GetInnerOrigin(), c);
	}
}

void TextView::RepairAll()
{
	StaticTextView::RepairAll();
	if (CanShowSelection())
		SetSelection(start.ch, end.ch, FALSE);
}

void TextView::ClearSelection(bool redraw)
{
	if (AnySelection()) {
		InvalidateSelection();
		start.ch= end.ch= -1;
		if (redraw)
			UpdateEvent();
	}
}

void TextView::ShowSelection(bool redraw, bool show)
{
	if (enabled != show) {
		enabled= show;
		InvalidateSelection();
		if (redraw)
			UpdateEvent();
	}
}

bool TextView::KbdFocus(bool in)
{
	ShowSelection(FALSE, in);
	return TRUE;
}

void TextView::Enable(bool enable, bool redraw)
{
	StaticTextView::Enable(enable, redraw);
	if (! enable)
		HideSelection(redraw);
}

void TextView::PrivSetSelection(SelPoint s, SelPoint e, bool redraw)
{
	InvalidateSelection();          // invalidate old selection
	start= s;
	end= e;
	InvalidateSelection();          // invalidate new selection
	if (redraw)
		UpdateEvent();
	Send(GetId(), cPartSelectionChanged, this);
}

void TextView::PrivSetSelection(int s, int e, bool redraw)
{
	SelPoint sp, ep;

	if (text == 0)
		return;
		
	int endp= text->End();
	sp.ch= Math::Range(0, endp, s);
	ep.ch= Math::Range(0, endp, e);
	sp.ch= CharToPoint(sp.ch, &sp.line, &sp.viewp);
	if (sp.ch != ep.ch)
		ep.ch= CharToPoint(ep.ch, &ep.line, &ep.viewp);
	else
		ep= sp;
	PrivSetSelection(sp, ep, redraw);
}

void TextView::SetSelection(int s, int e, bool redraw)
{
	DoneTyping();
	PrivSetSelection(s, e, FALSE);
	ShowSelection(redraw);
}

void TextView::SelectionAsString(byte *buf, int ma)
{
	GetText()->CopyInStr(buf, ma, start.ch, end.ch);
}

Text *TextView::SelectionAsText()
{
	return text->Save(start.ch, end.ch);
}

void TextView::SetDragAndDrop(bool on)
{
	SetFlag(eTextViewDragAndDrop, on);
}

void TextView::ShowLine(int line, int delay)
{
	LineMark *lm= MarkAtLine(Math::Range(0, NumberOfLines()-1, line-1));
	SetSelection(lm->Pos(), lm->End());
	RevealSelection();
	//Rectangle r(SelectionRect());
	//Scroll(cPartScrollAbs, Point(0, r.origin.y)+Point(0,2));
	if (delay > 0) {
		GraphicDelay(delay);
		SetSelection(lm->Pos(), lm->Pos(), TRUE);
	}
}

Rectangle TextView::BoundingRect(int from, int to)
{
	Point sp, ep;
	int sl, el;

	if(from == start.ch) {
		sp= start.viewp;
		sl= start.line;
	} else
		CharToPoint(from, &sl, &sp);
	if (to == end.ch) {
		ep= end.viewp;
		el= end.line;
	} else
		CharToPoint(to, &el, &ep);

	Point rp(ep.x+1, ep.y + LineHeight(el));
	Rectangle r= NormRect(sp, rp);
	r.origin+= GetInnerOrigin();
	return r;
}

void TextView::RevealSelection()
{
	if (AnySelection()) {
		Rectangle r= SelectionRect().Expand(Point(8,0));
		RevealRect(r, r.extent);
	}
}

Rectangle TextView::SelectionRect()
{
	if (AnySelection())
		return BoundingRect(start.ch, end.ch);
	return gRect0;
}

Command *TextView::DoLeftButtonDownCommand(Point lp, Token t, int clicks)
{
	SelPoint oldStart, oldEnd;
	TextRangeFP rf= CharacterRange;
		
	oldStart= start;
	oldEnd= end;

	if (! Enabled())
		return View::DoLeftButtonDownCommand(lp, t, clicks);

	if (clicks >= 3)
		rf= ParagraphRange;
	else if (clicks >= 2)
		rf= WordRange;

	if (t.Flags == eFlgShiftKey)
		return new ExtendTextRangeSelector(this, rf);

	if (clicks == 1) {
		if (TestFlag(eTextViewDragAndDrop)) {
			SelPoint n;
			PointToPoint(lp-GetInnerOrigin(), &n.viewp, &n.line, &n.ch);
			if (n.ch >= oldStart.ch && n.ch < oldEnd.ch)
				return new DragAndDropSelector(this, oldStart.ch, oldEnd.ch, t.Flags == eFlgCntlKey);
		}
		if (t.Flags == eFlgCntlKey)
			return new DragAndDropSelector(this, oldStart.ch, oldEnd.ch, TRUE);

		if (t.Flags == (eFlgShiftKey|eFlgCntlKey))
			return new QuickPasteSelector(this, oldStart.ch, oldEnd.ch);
	}
	if (t.Flags == 0)
		return new TextRangeSelector(this, rf);
	return View::DoLeftButtonDownCommand(lp, t, clicks);
}

void TextView::SetStopChars(char *stops)
{
	strreplace(&stopChars, stops);
}

Command *TextView::DoKeyCommand(int ch, Token)
{
	Command *cmd= gNoChanges;

	if (Writeable()) {
		GrSetCursor(eCrsNone);

		if (typing == 0)        // start new typing sequence?
			cmd= typing= MakeTypingCommand(cTYPEING, "Typing");

		if (ch == '\b') {
			if (cmd == typing && !Caret()) {
				// delete selection, no real typing
				timer->AddChar('\0');
			} else {
				typing->DelChar();
				timer->DelChar();
			}
		} else {
			if (ch >= 0 && ch < 0x1f)
				ch= inputmap[ch];
			if (ch != '\0') {
				typing->AddChar(1);
				timer->AddChar(ch);
				if (stopChars && strchr(stopChars, (byte)ch))
					FlushBatchedChars();
			}
		}
		if (timer->Size() >= cMaxBatchedIns || TestFlag(eTextViewNoBatch))
			FlushBatchedChars();
	}
	return cmd;
}

Command *TextView::DoCursorKeyCommand(EvtCursorDir cd, Token)
{
	Point p;
	int charNo= 0;

	if (!Enabled())
		return gNoChanges;
	switch (cd) {
	case eCrsLeft:
		charNo= start.ch-1;
		break;
	case eCrsRight:
		charNo= end.ch+1;
		break;
	case eCrsUp:
		charNo= CursorPos(start.ch, start.line, cd, p);
		break;
	case eCrsDown:
		charNo= CursorPos(end.ch, end.line, cd, p);
		break;
	}
	PrivSetSelection(charNo, charNo);
	DoneTyping();
	RevealSelection();
	return gNoChanges;
}

int TextView::CursorPos(int at, int line, EvtCursorDir d, Point p)
{
	int charNo;
	Point basePoint, screenPoint;

	CharToPoint(at, &line, &screenPoint);
	if (d == eCrsDown)
		line= Math::Min(nLines-1, line+1);
	else
		line= Math::Max(0, line-1);
	basePoint= LineToPoint(line, TRUE) + Point(screenPoint.x, 0);
	PointToPoint(basePoint, &p, &line, &charNo);
	return charNo;
}

GrCursor TextView::GetCursor(Point lp)
{
	if (TestFlag(eTextViewDragAndDrop)) {
		SelPoint n;
		PointToPoint(lp-GetInnerOrigin(), &n.viewp, &n.line, &n.ch);
		if (n.ch >= start.ch && n.ch < end.ch)
			return eCrsBoldArrow;
	}
	return eCrsIBeam;
}

void TextView::DoneTyping()
{
	typing= 0;
	text->ResetCurrentCharStyle();
}

void TextView::FlushBatchedChars()
{
	if (timer)
		timer->FlushBatchedChars();
}

void TextView::TypingDeleted(TypingCommand *t)
{
	if (typing == t)
		typing= 0;
}

TypingCommand *TextView::MakeTypingCommand(int cmdNo, char *name)
{
	return new TypingCommand(this, cmdNo, name);
}

bool TextView::DeleteRequest(int, int)
{
	return !IsReadOnly();
}

void TextView::Cut()
{
	if (!Writeable())
		return;
	if (DeleteRequest(start.ch, end.ch) && AnySelection()) {
		updateSelection= FALSE;
		text->Cut(start.ch, end.ch);
		updateSelection= TRUE;
		PrivSetSelection(start.ch, start.ch, FALSE);
	}
}

void TextView::Copy(Text *t)
{
	text->Copy(t, start.ch, end.ch);
}

void TextView::Paste(Text *t)
{
	if (Writeable()) {
		if ((!Caret() && !DeleteRequest(start.ch, end.ch)) || !AnySelection())
			return;
		updateSelection= FALSE;
		text->Paste(t, start.ch, end.ch);
		updateSelection= TRUE;
		PrivSetSelection(start.ch+t->End(), start.ch+t->End(), FALSE);
	}
}

Command *TextView::InsertText(Text *t)
{
	Command *cmd= gNoChanges;
	if (Writeable()) {
		if (typing == 0)
			cmd= typing= MakeTypingCommand(cTYPEING, "Typing");
		typing->AddChar(t->End());
		Paste(t);
	}
	return cmd;
}

Command *TextView::InsertString(const byte *str, int len)
{
	Text *t= text->MakeScratchText((byte*)str, len);
	Command *cmd= InsertText(t);
	SafeDelete(t);
	return cmd;
}

void TextView::DelChar(int n)
{
	int newStart= Math::Max(0, start.ch-n);
	//int changedLine= CharToLine(newStart);

	if (newStart == start.ch)
		return;
	if (newStart == 0 && start.ch == 0)
		return;
	if (DeleteRequest(newStart, start.ch)) {
		updateSelection= FALSE;
		text->Cut(newStart, start.ch);
		updateSelection= TRUE;
		PrivSetSelection(newStart, newStart, FALSE);
	}
}

Text *TextView::SetText(Text *t, bool scroll)
{
	Text *old= text;
	SetFlag(eTextViewModified);
	ForceRedraw();
	old->RemoveObserver(this);
	PrivSetSelection(0, 0, FALSE); // reset old selection
	text= t;
	text->AddObserver(this);
	RepairAll();
	if (scroll == cRevealTop)
		Scroll(cPartScrollAbs, gPoint0, FALSE);
	Send(GetId(), cPartReplacedText, 0);
	PrivSetSelection(0, 0, FALSE); // set new selection
	typing= 0;
	return old;
}

void TextView::SetString(byte *str, int len)
{
	ForceRedraw();
	PrivSetSelection(0, 0, FALSE);
	text->ReplaceWithStr(str, len);
	RepairAll();
	Send(GetId(), cPartReplacedText, 0);
	DoneTyping();
	PrivSetSelection(0, 0, FALSE);
	typing= 0;
}

void TextView::SetReadOnly(bool m)
{
	SetFlag(eTextViewReadOnly, m);
}

bool TextView::IsReadOnly()
{
	return TestFlag(eTextViewReadOnly);
}

void TextView::DoObserve(int, int part, void *what, Object *op)
{
	//int d= 0, d1= 0;

	if (op == text && part == cPartSenderDied) {
		text= 0;
		return;
	}
		
	if (op == text) {
		TextChanges tc= *(TextChanges*)what;
		SetFlag(eTextViewModified);
		switch (part) {
		case eTextChangedRange:
			marks->RangeChanged(tc.from, tc.to - tc.from);
			Repair(tc.from, tc.to, FALSE);
			if (updateSelection && AnySelection())
				PrivSetSelection(tc.from, tc.to, TRUE);
			break;

		case eTextDeleted:
		case eTextReplaced:
			marks->Replace(tc.from, tc.to, tc.size);
			Repair(tc.from, tc.from+tc.size, FALSE);
			if (updateSelection && AnySelection()) {
				int p= tc.from+tc.size;
				PrivSetSelection(p, p, TRUE);
			}
			break;
		}
		if (tc.pagination == cPaginationChange)
			ForceRedraw();
		Send(GetId(), cPartChangedText, this);    // notify observers of the textview
	}
}

bool TextView::PrintOnWhenObserving(Object *from)
{
	return GetText() != from;
}

IStream& TextView::ReadFrom(IStream &s)
{
	StaticTextView::ReadFrom(s);
	enabled= TRUE;
	GetText()->AddObserver(this);
	return s;
}

void TextView::NormSelection()
{
	if (start.ch > end.ch)
		SwapSelPoints(start, end);
}

//---- menu related commands

bool TextView::HasSelection()
{
	return AnySelection() && !Caret();
}

Command *TextView::PasteData(Data *data)
{
	Text *t;
	if ((t= (Text*) data->AsObject(GetText()->IsA())) && !IsReadOnly())
		return new PasteTextCommand(this, t);
	return gNoChanges;
}

Menu *TextView::MakeMenu(int menuId)
{
	int sz, font, size= gSysFont->Size();
	GrFont fid= (GrFont)gSysFont->Fid();
	const char *fontname;
	Menu *m;

	switch (menuId) {
	case cFONTMENU:
		m= new Menu(cFONTMENU, "Fonts", FALSE, TRUE, 0, 1, new SortedObjList);
		for (font= 0; fontname= gFontManager->IdToName((GrFont)font); font++)
			m->AppendItem((char*)fontname, cFIRSTFONT+font);
		break;

	case cSTYLEMENU:
		m= new Menu(cSTYLEMENU, "Styles", FALSE);
		m->Append(new MenuButtonItem(cFIRSTFACE+eFacePlain,
			new TextItem("Plain", new_Font(fid, size, eFacePlain))));
		m->Append(new MenuButtonItem(cFIRSTFACE+eFaceBold,
			new TextItem("Bold", new_Font(fid, size, eFaceBold)), "B"));
		m->Append(new MenuButtonItem(cFIRSTFACE+eFaceItalic,
			new TextItem("Italic", new_Font(fid, size, eFaceItalic)), "I"));
		m->Append(new MenuButtonItem(cFIRSTFACE+eFaceUnderline,
			new TextItem("Underline", new_Font(fid, size, eFaceUnderline)), "U"));
		m->Append(new MenuButtonItem(cFIRSTFACE+eFaceOutline,
			new TextItem("Outline", new_Font(fid, size, eFaceOutline))));
		m->Append(new MenuButtonItem(cFIRSTFACE+eFaceShadow,
			new TextItem("Shadow", new_Font(fid, size, eFaceShadow))));
		break;

	case cSIZEMENU:
		m= new Menu(cSIZEMENU, "Sizes", FALSE);
		for (sz= 9; sz <= 24; sz++)
			m->AppendItem(form("%d  Points", sz), cFIRSTSIZE+sz);
		break;

	case cFORMATMENU:
		m= new Menu(cFORMATMENU, "Format", FALSE);
		m->Append(new MenuButtonItem(cFIRSTADJUST,   "Align Left"));
		m->Append(new MenuButtonItem(cFIRSTADJUST+1, "Align Right"));
		m->Append(new MenuButtonItem(cFIRSTADJUST+2, "Align Center"));
		m->Append(new MenuButtonItem(cFIRSTADJUST+3, "Justify"));
		m->AppendItems("-", 0);
		m->Append(new MenuButtonItem(cFIRSTSPACING+0, "Single Spacing"));
		m->Append(new MenuButtonItem(cFIRSTSPACING+18, "1-1/2 Spacing"));
		m->Append(new MenuButtonItem(cFIRSTSPACING+24, "Double Spacing"));
		break;

	}
	return m;
}

void TextView::DoSetupMenu(Menu *m)
{
	View::DoSetupMenu(m);
	switch (m->GetId()) {
	case cEDITMENU:
		SetupEditMenu(m);
		break;
	case cSIZEMENU:
		SetupSizeMenu(m);
		break;
	case cFONTMENU:
		SetupFontMenu(m);
		break;
	case cFORMATMENU:
		SetupFormatMenu(m);
		break;
	case cSTYLEMENU:
		SetupCharStyleMenu(m);
		break;
	case cVIEWMENU:
		SetupViewMenu(m);
		break;
	}
}

void TextView::SetupEditMenu(Menu *m)
{
	if (!IsReadOnly() && !Caret())
		m->EnableItems(cCUT, cIMPORT, 0);
	if (AnySelection())
		m->EnableItems(cSELECTALL, cFIND, 0);
}

void TextView::SetupSizeMenu(Menu *m)
{
	Font *fp= GetCharStyle()->GetFont();
	m->CheckItem(cFIRSTSIZE+fp->Size());
}

void TextView::SetupFontMenu(Menu *m)
{
	Font *fp= GetCharStyle()->GetFont();
	m->CheckItem(gFontManager->NameToId(fp->Name())+cFIRSTFONT);
}

void TextView::SetupViewMenu(Menu *m)
{
	m->EnableItem(cSHOWINVIS);
	m->CheckItem(cSHOWINVIS, GetShowInvis(), cItemStateManyOf);
}

void TextView::SetupCharStyleMenu(Menu *m)
{
	GrFace face= GetCharStyle()->GetFont()->Face();
	m->CheckItem(cFIRSTFACE+eFacePlain, face == eFacePlain);
	m->CheckItem(cFIRSTFACE+eFaceBold, (face&eFaceBold) == eFaceBold, cItemStateManyOf);
	m->CheckItem(cFIRSTFACE+eFaceItalic, (face&eFaceItalic) == eFaceItalic, cItemStateManyOf);
	m->CheckItem(cFIRSTFACE+eFaceUnderline, (face&eFaceUnderline) == eFaceUnderline, cItemStateManyOf);
	m->CheckItem(cFIRSTFACE+eFaceOutline, (face&eFaceOutline) == eFaceOutline, cItemStateManyOf);
	m->CheckItem(cFIRSTFACE+eFaceShadow, (face&eFaceShadow) == eFaceShadow, cItemStateManyOf);
}

void TextView::SetupFormatMenu(Menu *m)
{
	int from;
	GetSelection(&from);
	ParaStyle *ps= text->GetParaStyle(from);
	m->CheckItem(ps->GetProperty(eTxtPAlign)+cFIRSTADJUST);
	m->CheckItem(ps->GetProperty(eTxtPSpacing)+cFIRSTSPACING);
}

Command *SetParaProp(TextView *view, int cmd, TxtParaProp p, int value)
{
	ParaDesc pd;
	pd.SetProperty(p, value);
	return
		new ParaStyleCommand(
			view,
			cmd,
			"Change Formatting",
			p,
			pd
		);
}

Command *TextView::DoMenuCommand(int cmd)
{
	if (cmd >= cFIRSTSIZE && cmd <= cLASTSIZE)
		return new CharStyleCommand(
					this,
					cmd,
					"Change Size",
					eTxtPSize,
					CharStyleSpec(
							eFontDefault,
							eFacePlain,
							(GrFace)(cmd-cFIRSTSIZE)
					)
		);

	if (cmd >= cFIRSTFONT && cmd <= cLASTFONT)
		return new CharStyleCommand(
					this,
					cmd,
					"Change Font",
					eTxTPFont,
					CharStyleSpec(
							(GrFont)(cmd-cFIRSTFONT),
							eFacePlain,
							0
					)
		);
		
	if (cmd >= cFIRSTFACE && cmd <= cLASTFACE) {
		return new CharStyleCommand(
					this,
					cmd,
					"Change Face",
					eTxTPFace,
					CharStyleSpec(
							eFontDefault,
							(GrFace)(cmd-cFIRSTFACE),
							0
					)
		);
	}

	if (cmd >= cFIRSTSPACING && cmd <= cLASTSPACING) {
		ParaDesc pd;
		pd.SetProperty(eTxtPSpacing, cmd-cFIRSTSPACING);
		return
			new ParaStyleCommand(
				this,
				cmd,
				"Change Formatting",
				eTxtPSpacing,
				pd
			);
	}

	/*
	if (cmd >= cFIRSTCOLOR && cmd <= cLASTCOLOR)
		return new CharStyleCommand(
					this,
					cmd,
					"Change Color",
					eTxtPInk,
					CharStyleSpec(
							eFontDefault,
							eFacePlain,
							0,
							palette[cmd-cFIRSTCOLOR]
					)
		);
	*/

	if (cmd >= cFIRSTADJUST && cmd <= cLASTADJUST)
		return SetParaProp(this, cmd, eTxtPAlign, cmd-cFIRSTADJUST);
		
	switch (cmd) {
	case cCUT:
	case cCOPY:
		gClipBoard->SelectionToClipboard(SelectionAsText(), FALSE);
		return new CutCopyTextCommand(this, cmd);
		
	case cSELECTALL:
		SelectAll();
		break;
		
	case cSHOWINVIS:
		ShowInvisibles(!GetShowInvis());
		return gNoChanges;

	default:
		return View::DoMenuCommand(cmd);
	}
	return gNoChanges;
}

bool TextView::SelectRegExpr(RegularExp *rex, bool dir)
{
	int selStart, selEnd, pos, matched;

	GetSelection(&selStart, &selEnd);
	if (dir == cSearchForward)
		pos= GetText()->Search(rex, &matched, selEnd);
	else
		pos= GetText()->Search(rex, &matched, Math::Max(0, selStart-1), cMaxInt, FALSE);

	if (pos != -1) {
		PrivSetSelection(pos, pos+matched);
		DoneTyping();
		return TRUE;
	}
	return FALSE;
}

void TextView::Home()
{
	PrivSetSelection(0, 0);
	DoneTyping();
}

void TextView::Bottom()
{
	PrivSetSelection(cMaxInt, cMaxInt);
	DoneTyping();
}

void TextView::SelectAll(bool redraw)
{
	PrivSetSelection(0, text->End(), redraw);
	DoneTyping();
}

CharStyle *TextView::GetCharStyle()
{
	int from, to;
	GetSelection(&from, &to);
	if (text->GetCurrentCharStyle())
		return text->GetCurrentCharStyle();
	else if (text->IsParaStart(from))
		from++;
	if (from == to)
		from--;
	return text->GetCharStyle(Math::Max(0, from));
}

